/**
 * Puzzle8.java
 *
 * Fecha de creación: Mar 11, 2012,6:26:53 PM
 *
 * Autor: José Ignacio Rivero Dávila
 */

package model;

import java.util.Random;
import util.Movement;
import util.MovementNotAllowedException;


public class Puzzle8Model extends AbstractModel implements PuzzleInterface {
    private int [] positions;
    private  int[] winningPositions = new int[9];;
    private int zeroPosition;
    private int numberOfLegalMoves = 0;
    private Movement lastMovement;
    
    /**
     * Generates a new puzzle with the positions of the final state
     */
    public Puzzle8Model(){
        fillWinningPositions();
        positions = winningPositions.clone();
        findAndSetZeroPosition();
    }
    public Puzzle8Model(int[] arrayOfInitialState, int [] arrayOfFinalState){
        this(arrayOfInitialState);
        this.winningPositions=arrayOfFinalState.clone();
        findAndSetZeroPosition();
    }
    public Puzzle8Model(int[] arrayOfInitialState){
        this.positions = arrayOfInitialState.clone(); 
        findAndSetZeroPosition();
    }
    private void findAndSetZeroPosition(){
        int i = 0;
        while(positions[i]!=0){
            i++;
        }
        setZeroPosition(i);
    }
    private void setZeroPosition(int x){
        if(x>=9){
            throw new RuntimeException("Zero cannot be larger than 9");
        }
        int oldZeroPosition = zeroPosition;
        zeroPosition=x;
        firePropertyChange("ZeroPosition", oldZeroPosition, x);
    }
            
    /*
     * Método exclusivamente para hacer pruebas
     * No se debe de usar porque no generá un puzzle que necesariamente
     * tenga solución
     */
    private void randomPuzzle(){
        boolean[] temp = new boolean [9];
        Random rnd = new Random();
        int number=rnd.nextInt(9);
        for (int i = 0; i < positions.length; i++) {
            while(temp[number]){
                number = rnd.nextInt(9);   
            }
            if(number==0)
                zeroPosition=i;
            setNumber(number,i);
            temp[number]=true;
        }        
    }
    private void setNumber(int number, int position){
        positions[position] = number;
    }
    private void fillWinningPositions(){
        winningPositions[0]=1;
        winningPositions[1]=2;
        winningPositions[2]=3;
        winningPositions[3]=4;
        winningPositions[4]=5;
        winningPositions[5]=6;
        winningPositions[6]=7;
        winningPositions[7]=8;
        winningPositions[8]=0;
    }
    @Override
    public boolean isWinner(){
        return java.util.Arrays.equals(positions, winningPositions);
    }

    @Override
    public Integer[] getPositions() {
        Integer[] pos=new Integer[positions.length];
        for (int i = 0; i < positions.length; i++) {
            pos[i]=positions[i];
        }
        return pos;
    }

    @Override
    public void setPositions(Integer[] positions) {
        Integer [] oldValue = new Integer[9];
        for (int i = 0; i < positions.length; i++) {
            oldValue[i]=new Integer(this.positions[i]);
           this.positions[i] = positions[i].intValue();
        }
        firePropertyChange("Positions", oldValue, positions);
        
    }

    private void moveLeft() throws MovementNotAllowedException{
        //sólo se puede mover a la izquierda si está en una posición
        //cuyo modulo 3 es mayor a 0
        if(getZeroPosition()%3==0)
            throw new MovementNotAllowedException(Movement.LEFT);
        moveZero(getZeroPosition()-1);
    }

    private void moveRight() throws MovementNotAllowedException{
        //sólo se puede mover a la derecha si está en una posición
        //cuyo modulo 3 es 0 o 1
        if(getZeroPosition()%3 == 2)
            throw new MovementNotAllowedException(Movement.RIGHT);
        moveZero(getZeroPosition()+1);   
    }

    private void moveUp() throws MovementNotAllowedException{
        //sólo se puede mover hacia arriba si la posiciòn del zero es
        //mayor a dos
        if(getZeroPosition()<=2){
            throw new MovementNotAllowedException(Movement.UP);
        }
        moveZero(getZeroPosition()-3);
    }

    private void moveDown() throws MovementNotAllowedException{
        //sólo se puede mover hacia abajo si la posiciòn del zero es
        //menor a 6
        if(getZeroPosition()>=6){
            throw new MovementNotAllowedException(Movement.DOWN);
        }
        moveZero(getZeroPosition()+3);
    }
    private void moveZero(int newZeroPosition){
        int oldZeroPosition = getZeroPosition();
        switchN(oldZeroPosition,newZeroPosition);
        zeroPosition=newZeroPosition;
        firePropertyChange("ZeroPosition", oldZeroPosition, newZeroPosition);
    }

    /**
     * Cambia los número de las posiciones x y y. No checa que sea un movimiento
     * válido, sólo lo hace.
     * @param x
     * @param y 
     */
    private void switchN(int x, int y){
        Integer[] oldValue = intToInteger(positions);
        int temp = getNumberAtPosition(x);
        setNumber(getNumberAtPosition(y), x);
        setNumber(temp, y);
        Integer[] newValue = intToInteger(positions);
        firePropertyChange("Positions", oldValue, newValue);
        
    }
    @Override
    public String toLispCompatible(){
        StringBuilder sbCurrent = new StringBuilder();
        sbCurrent.append("(SETQ iniState '(");
        for (Integer integer : positions) {
            sbCurrent.append(integer).append(" ");
        }
        sbCurrent.deleteCharAt(sbCurrent.length()-1);
        sbCurrent.append(")");
        StringBuilder sbFinal = new StringBuilder();
        sbFinal.append(" goal '(");
        for (Integer integer : winningPositions) {
            sbFinal.append(integer).append(" ");
        }
        sbFinal.deleteCharAt(sbFinal.length()-1);
        sbFinal.append("))");
        return sbCurrent.toString() + sbFinal.toString();
    }
    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < positions.length; i++) {
            sb.append(positions[i]).append(" ");
            if((i+1)%3==0)
                sb.append(System.getProperty("line.separator"));  
        }
        return sb.toString();
    }
    public static void main(String args[]){
        Puzzle8Model puzzle = new Puzzle8Model();
        //puzzle.randomPuzzle();
        System.out.println(puzzle.toLispCompatible());
        System.out.println(puzzle.getZeroPosition());
        System.out.println(puzzle.getZeroPosition()%3);
        try{
            puzzle.moveUp();
            puzzle.moveLeft();
        }catch (Exception ex){
            ex.printStackTrace();
            
        }
        System.out.println(puzzle.toString());
    }

    @Override
    public Integer getZeroPosition() {
        return new Integer(zeroPosition);
    }
    @Override
    public Integer getNumberAtPosition(int position){
        return new Integer(positions[position]);
    }
    private Integer[] intToInteger (int[] x){
        Integer[] result = new Integer[x.length];
        for (int i = 0; i < x.length; i++) {
            result[i]= new Integer(x[i]);
            
        }
        return result;
    }

    @Override
    public void move(Movement move) throws MovementNotAllowedException {
        switch (move){
                case UP: moveUp(); break;
                case DOWN: moveDown(); break;
                case LEFT: moveLeft(); break;
                case RIGHT: moveRight(); break;
            }
        firePropertyChange("NumberOfLegalMoves", new Integer(numberOfLegalMoves),
                new Integer(++numberOfLegalMoves));
        Movement oldLastMovement = lastMovement;
        lastMovement = move;
        firePropertyChange("LastMovement", oldLastMovement, move);
    }
    
    

}
